import logging
from collections import OrderedDict

from django.contrib.sessions.backends.base import CreateError
from django.contrib.sessions.backends.db import SessionStore
from django.contrib.sessions.models import Session
from django.core.exceptions import SuspiciousOperation
from django.db import router, transaction
from django.utils import timezone
from django.utils.crypto import salted_hmac
from django.utils.encoding import force_text
from pymysql import IntegrityError

from django.conf import settings


class ItsmSessionStore(SessionStore):
    using = 'itsm_db'

    def load(self):
        try:
            s = Session.objects.using(self.using).get(
                session_key=self.session_key,
                expire_date__gt=timezone.now()
            )
            return self.decode(s.session_data)
        except (Session.DoesNotExist, SuspiciousOperation) as e:
            if isinstance(e, SuspiciousOperation):
                logger = logging.getLogger('django.security.%s' %
                        e.__class__.__name__)
                logger.warning(force_text(e))
            self._session_key = None
            return {}

    def exists(self, session_key):
        return Session.objects.using(self.using).filter(session_key=session_key).exists()

    def _hash(self, value):
        key_salt = "django.contrib.sessionsSessionStore"
        return salted_hmac(key_salt, value, settings.ITSM_APP_TOKEN).hexdigest()

    def _get_session(self, no_load=False):
        """
        Lazily loads session from storage (unless "no_load" is True, when only
        an empty dict is stored) and stores it in the current instance.
        """
        self.accessed = True
        try:
            return self._session_cache
        except AttributeError:
            if self.session_key is None or no_load:
                self._session_cache = OrderedDict()
            else:
                self._session_cache = self.load()
        return self._session_cache

    def save(self, must_create=False):
        """
        Saves the current session data to the database. If 'must_create' is
        True, a database error will be raised if the saving operation doesn't
        create a *new* entry (as opposed to possibly updating an existing
        entry).
        """
        if self.session_key is None:
            return self.create()
        obj = Session(
            session_key=self._get_or_create_session_key(),
            session_data=self.encode(self._get_session(no_load=must_create)),
            expire_date=self.get_expiry_date()
        )
        try:
            with transaction.atomic(using=self.using):
                obj.save(force_insert=must_create, using=self.using)
        except IntegrityError:
            if must_create:
                raise CreateError
            raise

    def delete(self, session_key=None):
        if session_key is None:
            if self.session_key is None:
                return
            session_key = self.session_key
        try:
            Session.objects.using(self.using).get(session_key=session_key).delete()
        except Session.DoesNotExist:
            pass

    @classmethod
    def clear_expired(cls):
        Session.objects.using(cls.using).filter(expire_date__lt=timezone.now()).delete()